﻿#region ... Copyright Notice ...
/*
   Copyright 2008 Tyler Jensen

	Author: Tyler Jensen    http://www.tsjensen.com/blog

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
#endregion

using System;
using System.Data;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Xml.Linq;
using Atrax.Contracts;
using Atrax.Library;
using Atrax.Logging;
using Atrax.Consumers;

namespace Atrax.Master
{
	public class RequestHandler
	{
		IQueryProcessor processor;
		object locker = new object(); //use for locking results collections

		public int StepsCompleted { get; set; }

		//protected abstract List<SpiderRecord> CreateRequest();
		//protected abstract AtraxReportFragment ProcessResponseData(int spiderIndex, AtraxSpiderRequest origReq, AtraxSpiderResponseData responseData, bool mayAddMoreSpiderQuerys);
		//protected abstract AtraxReport CreateReport(List<SpiderRecord> list);
		//internal abstract bool RequiresMultipleSpiderCalls { get; }

		public IQueryProcessor Processor { get { return processor; } set { processor = value; } }

		//protected AtraxQueryRequestData m_request;
		protected List<SpiderResponse> spiderResponses = new List<SpiderResponse>();
		protected int spiderQuerysToBeCompleted = 0;
		protected DateTime lastPing = DateTime.MinValue;

		//called by managed thread pool to start processing when there is a thread available
		public void ThreadPoolCallback(object threadContext)
		{
			processor = (IQueryProcessor)threadContext;
			try
			{
				//m_request = data;
				//m_request.BeganProcessing = DateTime.Now;
				ProcessQuery();
			}
			catch (Exception e)
			{
				Log.App(LogEvents.RequestHandlerFailed, "queryid=" + processor.Query.Header.QueryId + " | querytype=" + processor.Query.Header.QueryType + " | licensekey=" + processor.Query.Header.LicenseKey, e);
			}
		}

		//called by controller if time is 1/2 past the expiration time
		public void PingNearlyExpired()
		{
			//on a separate thread (if not ping'd before or enough), launch cleanup/housekeeping method to relaunch any dead requests
			TimeSpan ts = DateTime.Now - lastPing;
			lastPing = DateTime.Now;
			if (ts.TotalSeconds > 20) //run every 20 seconds
			{
				PingManagedThreadPool.QueueUserWorkItem(ProcessPing, lastPing);
			}
		}

		public void ProcessPing(object threadContext) //called on a separate thread pool thread, so lock where necessary to iterate through collections
		{
			lock (locker)
			{
				try
				{
					DateTime timeQueued = (DateTime)threadContext;
					for (int i = 0; i < spiderResponses.Count; i++)
					{
						SpiderResponse resp = spiderResponses[i];
						if (resp.IsPastDue() && !resp.IsComplete)
						{
							//re-issue request and set new response
							SpiderClient spider = null;
							try
							{
								spider = SpiderClientFactory.Create();
								spiderResponses[i] = spider.Execute(resp.Request);
								spider.Close();
							}
							catch
							{
								if (spider != null) spider.Abort();
							}
						}
					}
				}
				catch (Exception e)
				{
					string queryId = processor.Query.Header.QueryId;
					string queryType = processor.Query.Header.QueryType;
					string licenseKey = processor.Query.Header.LicenseKey;

					Log.App(LogEvents.RequestHandlerProcessPingFailed,
						"queryid=" + queryId + " | querytype=" + queryType + " | licensekey=" + licenseKey, e);
				}
				lastPing = DateTime.Now; //update to make sure it does not get called and called
			}
		}

		void ProcessQuery()
		{
			//Log.Debug(string.Format("RequestHandler.ProcessRequest called for {0}", m_request.QueryId));
			//Note:  use the spiderQueryId to track each request 
			//use this.m_request for the AtraxQueryRequestData data
			string queryId = string.Empty;
			string queryType = string.Empty;
			string licenseKey = string.Empty;
			try
			{
				InternetRequest[] requests = processor.CreateRequests(); //this is the unique generation of requests
				if (requests != null && requests.Length > 0)
				{
					//assure that each request has query header if query processor failed to add it
					foreach (InternetRequest r in requests)
					{
						if (r.Header == null) r.Header = processor.Query.Header;
					}

					queryId = processor.Query.Header.QueryId;
					queryType = processor.Query.Header.QueryType;
					licenseKey = processor.Query.Header.LicenseKey;
					List<SpiderRequest> srList = new List<SpiderRequest>();
					foreach (InternetRequest r in requests)
					{
						SpiderRequest sr = new SpiderRequest()
						{
							AuthenticationToken = processor.Query.Header.AuthenticationToken,
							QueryId = processor.Query.Header.QueryId,
							Request = r
						};
						srList.Add(sr);
					}
					LaunchNewRequests(srList);
				}
			}
			catch (Exception e)
			{
				Log.App(LogEvents.RequestHandlerProcessRequestFailure,
					"queryid=" + queryId + " | querytype=" + queryType + " | licensekey=" + licenseKey, e);
			}
		}

		void LaunchNewRequests(List<SpiderRequest> list)
		{
			//call spider and get spiderqueryid for each and associate with pagenum stored in global
			for (int i = 0; i < list.Count; i++)
			{
				SpiderClient spider = null;
				try
				{
					spider = SpiderClientFactory.Create();
					spiderResponses.Add(spider.Execute(list[i]));
					spider.Close();
				}
				catch
				{
					if (spider != null) spider.Abort();
				}
				spiderQuerysToBeCompleted++;
			}
		}

		/// <summary>
		/// Called by the SpiderCallbackProcessor if the RequestHandler exists in memory still.
		/// </summary>
		/// <param name="responseData"></param>
		public void ProcessSpiderCallback(SpiderResult result)
		{
			//Note:  handle callback request and call AtraxRequestController.CompleteQuery if finished, 
			//       otherwise store result data in local var for processing when query is "complete"
			//Note:  use safe threading on collections because there is a chance this could be called
			//       multiple times on separate threads
			lock (locker)
			{
				//Log.Debug(string.Format("RequestHandler.ProcessCallback called for {0} on spider {1}", m_request.QueryId, responseData.SpiderRequestId));
				//responseData.SpiderRequestId = the AtraxSpiderResponse.SpiderRequestId received when making the initial request
				//use to track multiple requests
				try
				{
					int spiderIndex = 0;
					SpiderResponse response = MatchSpiderResponseToSpiderResult(result, out spiderIndex);
					if (response != null) //if it's not found, just ignore it cuz there is no match
					{
						StepsCompleted++;
						response.IsComplete = true; //be sure to set this so we don't restart later on a ping

						QueryResultFragment fragment = processor.ProcessResponse(result.Response);

						//add any new requests (these are followon requests made by the ProcessResponse method
						if (fragment.AdditionalRequests != null && fragment.AdditionalRequests.Length > 0)
						{
							List<SpiderRequest> srList = new List<SpiderRequest>();
							foreach (InternetRequest r in fragment.AdditionalRequests)
							{
								SpiderRequest sr = new SpiderRequest()
								{
									AuthenticationToken = processor.Query.Header.AuthenticationToken,
									QueryId = processor.Query.Header.QueryId,
									Request = r
								};
								srList.Add(sr);
							}
							LaunchNewRequests(srList);
						}
						response.Fragment = fragment; //assign to response so fragments can be sent to processor in order or response creation
						spiderQuerysToBeCompleted--; //decrement querys completed to determine if results are ready to be processed
						if (spiderQuerysToBeCompleted < 1)	//we're done, finito, wrap it up
						{
							ProcessQueryCompletion();
						}
					}
				}
				catch (Exception e)
				{
					Log.App(LogEvents.RequestHandlerProcessCallbackFailure,
						"queryid=" + result.QueryId + " | spiderqueryid=" + result.SpiderQueryId, e);
				}
			}
		}

		void ProcessQueryCompletion()
		{
			string queryId = string.Empty;
			string queryType = string.Empty;
			string licenseKey = string.Empty;
			try
			{
				List<QueryResultFragment> list = new List<QueryResultFragment>();
				foreach (SpiderResponse r in spiderResponses)
				{
					list.Add(r.Fragment);
				}
				QueryResult result = processor.CreateResult(list.ToArray());

				queryId = result.Query.Header.QueryId;
				queryType = result.Query.Header.QueryType;
				licenseKey = result.Query.Header.LicenseKey;

				if (result.Query == null) result.Query = processor.Query; //just make sure it was assigned by query processor

				CallbackSender cbs = new CallbackSender();
				cbs.SendCallback(result);
				QueryController.CompleteQuery(result.Query.Header.QueryId);

				//now log our success
				Log.App(LogEvents.RequestHandlerQueryComplete, "queryid=" + result.Query.Header.QueryId
					+ " | querytype=" + result.Query.Header.QueryType + " | licensekey=" + result.Query.Header.LicenseKey);
			}
			catch (Exception e)
			{
				Log.App(LogEvents.RequestHandlerQueryCompletionException, "queryid=" + queryId
					+ " | querytype=" + queryType + " | licensekey=" + licenseKey, e);
				//todo: send email
			}
		}

		SpiderResponse MatchSpiderResponseToSpiderResult(SpiderResult result, out int spiderIndex)
		{
			spiderIndex = 0;
			for (int i = 0; i < spiderResponses.Count; i++)
			{
				if (spiderResponses[i].SpiderQueryId == result.SpiderQueryId)
				{
					spiderIndex = i;
					return spiderResponses[i];
				}
			}
			return null;
		}
	}
}
